﻿using System;

using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Serialization.Formatters;
using System.Threading;
using Valentia.Kinection.DataTypes;
using Valentia.Kinection.Remoting.Proxy;

namespace Valentia.Kinection.Remoting.Helper
{
    /// <summary>
    /// This class will act as remoting server
    /// </summary>
    public class Server : MarshalByRefObject, IRemotingServerObject
    {
        #region Declarations

        /// <summary>
        /// Delegate for devices connection information to be sent.
        /// </summary>
        public delegate void CommandFromClientReceivedDlg(ClientCommands command, String loginName, String password);

        /// <summary>
        /// This event will be fired when server needs to send information of devices connection to client.
        /// </summary>
        public event CommandFromClientReceivedDlg CommandFromClientReceived;
        
        /// <summary>
        ///  Used to communicate with a remote object.
        /// </summary>
        private ObjRef internalServerReference;

        /// <summary>
        /// Server channel for remote calls that uses the TCP protocol to transmit messages.
        /// </summary>
        private TcpServerChannel serverChannel;

        /// <summary>
        /// Remoting port number.
        /// </summary>
        private int serverPortNumber = 15000;
        
        /// <summary>
        /// Remoting channel name.
        /// </summary>
        private String serverChannelName = "serverExample.Rem";

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="portNumber">Remoting Server Port</param>
        /// <param name="channelName">Remoting Channel Name</param>
        public Server(int portNumber, string channelName)
        {
            this.serverPortNumber = portNumber;
            this.serverChannelName = channelName;
        }

        #endregion

        #region IRemotingServerObject Members

        #region Events

        /// <summary>
        /// This event will be fired when server needs to send information of devices connected to service.
        /// </summary>
        public event DeviceConnectivityInfo KinectDeviceInfoReceived;

        /// <summary>
        /// This event will be fired when server needs to send information of client connected to service.
        /// </summary>
        public event ClientConnectivityInfo ServiceClientInfoReceived;

        /// <summary>
        /// This event will be fired when server needs to send information of service client and device connectivity status.
        /// </summary>
        public event ConnectionStatus ConnectionStatusInfoReceived;

        /// <summary>
        /// This event will be fired when server needs to show notification on service manager utility.
        /// </summary>
        public event EventHandler<NotificationReceivedArgs> ShowNotificationReceived; 

        #endregion

        #region Methods

        /// <summary>
        /// This method will be use to send command from client to server
        /// </summary>
        /// <param name="command">Command</param>
        /// <param name="loginName">Login name</param>
        /// <param name="password">Password</param>
        public String CommandFromClient(ClientCommands command, String loginName, String password)
        {
            ThreadPool.QueueUserWorkItem(state =>
                {
                    if (CommandFromClientReceived != null)
                    {
                        CommandFromClientReceived(command, loginName, password);
                    }
                });

            return null;
        }

        /// <summary>
        /// This method will be use to determine client and server connection is alive
        /// </summary>
        /// <returns>Server will return true</returns>
        public bool IsConnectionAlive()
        {
            return true;
        }

        #endregion

        #endregion

        #region Public Methods

        /// <summary>
        /// Obtains a lifetime service object to control the lifetime policy for this instance.
        /// </summary>
        /// <returns>NULL</returns>
        public override object InitializeLifetimeService()
        {
            return null;
        }

        /// <summary>
        /// Starts remoting server and register channel for communication
        /// </summary>
        public void Start()
        {
            if (IsServerRunning)
                return;

            Hashtable props = new Hashtable();
            props["name"] = serverChannelName;
            props["port"] = serverPortNumber;


            BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider();
            serverProv.TypeFilterLevel = TypeFilterLevel.Full;

            //try
            //{
            serverChannel = new TcpServerChannel(props, serverProv);

            if (!ChannelServices.RegisteredChannels.Contains(serverChannel))
            {
                ChannelServices.RegisterChannel(serverChannel, false);
            }
            else
            {
                serverChannel = (TcpServerChannel)ChannelServices.GetChannel(serverChannelName);
            }
            //}
            //catch {  }

            internalServerReference = RemotingServices.Marshal(this, props["name"].ToString());
            IsServerRunning = true;
        }

        /// <summary>
        /// Stops remoting server and free resources
        /// </summary>
        public void Stop()
        {
            if (!IsServerRunning)
                return;

            RemotingServices.Unmarshal(internalServerReference);
            ChannelServices.UnregisterChannel(serverChannel);

            internalServerReference = null;
            serverChannel = null;

            IsServerRunning = false;
        }

        /// <summary>
        /// Raises device connectivity status list event at remoting client end
        /// </summary>
        /// <param name="deviceConnectionInfo">List of devices</param>
        /// <param name="errorMessage">Error Message</param>
        public void RaiseDeviceConnectivityInfoEventOnClient(List<KinectDeviceInfo> deviceConnectionInfo
            , String errorMessage)
        {
            if (!IsServerRunning)
                throw new Exception("Server is not running");

            if (KinectDeviceInfoReceived == null)
                throw new Exception("No listener exists for KinectDeviceInfoReceived");

            DeviceConnectivityInfo listener = null;
            
            Delegate[] dels = KinectDeviceInfoReceived.GetInvocationList();

            foreach (Delegate del in dels)
            {
                try
                {
                    listener = (DeviceConnectivityInfo)del;
                    listener.Invoke(deviceConnectionInfo, errorMessage);
                }
                catch 
                {
                    //Could not reach the destination, so remove it
                    //from the list
                    KinectDeviceInfoReceived -= listener;
                }
            }
        }

        /// <summary>
        /// Raises service client connectivity status list event at remoting client end
        /// </summary>
        /// <param name="clientConnectionInfo">List of service clients</param>
        /// <param name="errorMessage">Error Message</param>
        public void RaiseClientConnectivityInfoEventOnClient(List<ServiceClientInfo> clientConnectionInfo
             , String errorMessage)
        {
            if (!IsServerRunning)
                throw new Exception("Server is not running");

            if (ServiceClientInfoReceived == null)
                throw new Exception("No listener exists for ServiceClientInfoReceived");

            ClientConnectivityInfo listener = null;
            Delegate[] dels = ServiceClientInfoReceived.GetInvocationList();

            foreach (Delegate del in dels)
            {
                try
                {
                    listener = (ClientConnectivityInfo)del;
                    listener.Invoke(clientConnectionInfo, errorMessage);
                }
                catch  
                {
                    //Could not reach the destination, so remove it
                    //from the list
                    ServiceClientInfoReceived -= listener;
                }
            }
        }

        /// <summary>
        /// Raises connnectivity status event at remoting client end
        /// </summary>
        /// <param name="connectivityStatus">Connectivity Status</param>
        /// <param name="connectedDeviceCount">Connected Device Count</param>
        /// <param name="connectedClientCount">Connected Client Count</param>
        /// <param name="errorMessage">Error Message</param>
        public void RaiseConnectionStatusInfoEventOnClient(ConnectivityStatus connectivityStatus
            , int connectedDeviceCount, int connectedClientCount, String errorMessage)
        {
            if (!IsServerRunning)
                throw new Exception("Server is not running");

            if (ConnectionStatusInfoReceived == null)
                throw new Exception("No listener exists for ConnectionStatusInfoReceived");

            ConnectionStatus listener = null;
            Delegate[] dels = ConnectionStatusInfoReceived.GetInvocationList();

            foreach (Delegate del in dels)
            {
                try
                {
                    listener = (ConnectionStatus)del;

                    listener.Invoke(connectivityStatus, connectedDeviceCount, connectedClientCount, errorMessage);
                }
                catch 
                {
                    //Could not reach the destination, so remove it
                    //from the list
                    ConnectionStatusInfoReceived -= listener;
                }
            }
        }

        /// <summary>
        /// Raises Notification event at remoting client end
        /// </summary>
        /// <param name="notificationArgs">Notification Event Args</param>
        public void RaiseNotificationEventOnClient(NotificationReceivedArgs notificationArgs)
        {
            if (!IsServerRunning)
                throw new Exception("Server is not running");

            if (ShowNotificationReceived == null)
                throw new Exception("No listener exists for ShowNotificationReceived");

            EventHandler<NotificationReceivedArgs> listener = null;

            // Getting all the registered delegates
            Delegate[] dels = ShowNotificationReceived.GetInvocationList();

            foreach (Delegate del in dels)
            {
                try
                {
                    listener = (EventHandler<NotificationReceivedArgs>)del;
                    listener.Invoke(null, notificationArgs);
                }
                catch
                {
                    //Could not reach the destination, so remove it
                    //from the list
                    ShowNotificationReceived -= listener;
                }
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Shows server is running or stopped
        /// </summary>
        public bool IsServerRunning { get; set; }

        #endregion
    }
}
